package ru.chaykin.quadtree;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import ru.chaykin.primitives.Circle;
import ru.chaykin.primitives.Point;
import ru.chaykin.viewobject.ViewObject;
import ru.chaykin.viewobject.geometry.Geometry;

/**
 * Класс дерева объектов. Реализует логику манипулирования данными.
 * @author k.chaykin
 *
 */
public class QuadTree {
	
	private static final float ADJASTING_RADIUS = 1;	//TODO Вынести в настройки радиус прилипания
	
	private static QuadElement root;
	private static Circle circle;
	private static GL10 gl;
	
	private float minRad = Float.MAX_VALUE;
	private float dst;
	private float[] p;
	private float[] adjPoint = new float[2];
	
	private List<ViewObject> objList;

	public QuadTree(QuadElement root) {
		QuadTree.root = root;
	}
	
	public static void setGL(GL10 gl) {
		QuadTree.gl = gl;
	}
	
	/**
	 * Выводит все объекты, попадающие в область видимости, заданной параметром <code>circle</code>. 
	 * Использует для вывода переданную в методе <code>setGL</code> переменную
	 * @param circle - область видимости.
	 */
	public void drawObjects(Circle circle) {
		QuadTree.circle = circle;
		synchronized(root) {
			drawObjects(root);
		}
	}
	
	/**
	 * Метод выравнивает точку до ближайшей видимой вершины, если она ближе радиуса "прилипания"
	 */
	public float[] adjastPoint(float x, float y, Circle circle) {
		QuadTree.circle = circle;
		minRad = Float.MAX_VALUE;
		this.p = new float[]{x, y};
		synchronized(root) {
			adjastPoint(root);
		}
		return minRad >= ADJASTING_RADIUS ? p : adjPoint; //Если точка дальше, чем радиус "прилипания", то не меняем ее координаты
	}
	
	public List<ViewObject> getRenderedObjects(Circle circle) {
		objList = new ArrayList<ViewObject>(1024);
		QuadTree.circle = circle;
		synchronized(root) {
			findObjects(root);
		}
		return objList;
	}
	
	public void clearSelection() {
		synchronized(root) {
			clearSelection(root);
		}
	}
	
	private void clearSelection(QuadElement quadEl) {
		if (quadEl.getObjs() == null) { //Вероятно, содержит поддеревья, но может быть и пустым листом
			QuadElement[] quadEls = quadEl.getChildren();
			if (quadEls != null) { //Точно содержит поддеревья
				for (QuadElement el : quadEls) {
					if (el.getBoundingBox().in(circle))
						clearSelection(el);
				}
			}
		}
		else { //Нашли лист дерева
			List<ViewObject> leafObjs = quadEl.getObjs();
			for (ViewObject obj : leafObjs) {
				obj.setSelected(false);
			}
		}
	}
	
	private void findObjects(QuadElement quadEl) {
		if (quadEl.getObjs() == null) { //Вероятно, содержит поддеревья, но может быть и пустым листом
			QuadElement[] quadEls = quadEl.getChildren();
			if (quadEls != null) { //Точно содержит поддеревья
				for (QuadElement el : quadEls) {
					if (el.getBoundingBox().in(circle))
						findObjects(el);
				}
			}
		}
		else { //Нашли лист дерева
			List<ViewObject> leafObjs = quadEl.getObjs();
			for (ViewObject obj : leafObjs) {
				if (obj.getBoundCircle().in(circle)) {
					objList.add(obj);
				}
			}
		}
	}
	
	private void adjastPoint(QuadElement quadEl) {
		if (quadEl.getObjs() == null) { //Вероятно, содержит поддеревья, но может быть и пустым листом
			QuadElement[] quadEls = quadEl.getChildren();
			if (quadEls != null) { //Точно содержит поддеревья
				for (QuadElement el : quadEls) {
					if (el.getBoundingBox().in(circle))
						adjastPoint(el);
				}
			}
		}
		else { //Нашли лист дерева
			List<ViewObject> leafObjs = quadEl.getObjs();
			for (ViewObject obj : leafObjs) {
				if (obj.getBoundCircle().in(circle)) {
					FloatBuffer buff = ((Geometry)obj).getBuff();
					for (int i = 0; i < buff.position(); i += 3) {
						dst = Point.distance(p[0], p[1], buff.get(i), buff.get(i + 1));
						if (dst < minRad) {
							minRad = dst;
							adjPoint[0] = buff.get(i);
							adjPoint[1] = buff.get(i + 1);
						}
					}
				}
			}
		}
	}
		
	private void drawObjects(QuadElement quadEl) {
		if (quadEl.getObjs() == null) { //Вероятно, содержит поддеревья, но может быть и пустым листом
			QuadElement[] quadEls = quadEl.getChildren();
			if (quadEls != null) { //Точно содержит поддеревья
				for (QuadElement el : quadEls) {
					if (el.getBoundingBox().in(circle))
						drawObjects(el);
				}
			}
		}
		else { //Нашли лист дерева
			List<ViewObject> leafObjs = quadEl.getObjs();
			for (ViewObject obj : leafObjs) {
				if (obj.getBoundCircle().in(circle)) {
					obj.draw(gl);
				}
			}
		}
	}
}
